package com.example.covid19.config;

import cn.hutool.core.date.DateTime;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.covid19.entity.Dynamique;
import com.example.covid19.service.DynamiqueService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import javax.annotation.PreDestroy;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.*;

/**
 * @author
 */
@Slf4j
@Component
public class SchedulingConfig implements SchedulingConfigurer {

    private final ApplicationContext applicationContext;
    private final DynamiqueService sysCronService;

    private static volatile ScheduledTaskRegistrar registrar;
    private static final ConcurrentHashMap<Integer, ScheduledFuture<?>>
            SCHEDULED_FUTURE_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<Integer, CronTask>
            CRON_TASK_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>();

    public SchedulingConfig(ApplicationContext applicationContext, DynamiqueService sysCronService) {
        this.applicationContext = applicationContext;
        this.sysCronService = sysCronService;
    }


    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        List<Dynamique> sysCronList = sysCronService.list(
                new QueryWrapper<Dynamique>().eq("is_enable", 1)
        );
        int size = sysCronList.size();
        ScheduledExecutorService pool = new ScheduledThreadPoolExecutor(
                size, new BasicThreadFactory.Builder()
                .namingPattern("thread-pool-%d").daemon(true)
                .build()
        );
        taskRegistrar.setScheduler(pool);
        registrar = taskRegistrar;
        refresh(sysCronList);
    }

    public void refresh(List<Dynamique> sysCronList) {
        Set<Integer> ids = SCHEDULED_FUTURE_CONCURRENT_HASH_MAP.keySet();
        ids.forEach(v -> {
            if (!exists(sysCronList, v)) {
                SCHEDULED_FUTURE_CONCURRENT_HASH_MAP.get(v).cancel(false);
            }
        });
        for (Dynamique v:
                sysCronList) {
            String cron = v.getCron();
            if (SCHEDULED_FUTURE_CONCURRENT_HASH_MAP.containsKey(v.getId())
                    && CRON_TASK_CONCURRENT_HASH_MAP.get(v.getId()).getExpression().equals(cron)) {
                continue;
            }
            if (SCHEDULED_FUTURE_CONCURRENT_HASH_MAP.containsKey(v.getId())) {
                SCHEDULED_FUTURE_CONCURRENT_HASH_MAP.get(v.getId()).cancel(false);
                SCHEDULED_FUTURE_CONCURRENT_HASH_MAP.remove(v.getId());
                CRON_TASK_CONCURRENT_HASH_MAP.remove(v.getId());
            }
            CronTask cronTask = new CronTask(
                    () -> {
                        log.info("ID: " + v.getId());
                        log.info("System cron time: " + new Date(System.currentTimeMillis()));
                        log.info("Thread ID: " + Thread.currentThread().getId());
                        log.info("Thread Name: " + Thread.currentThread().getName());
                        if (v.getClassName() == null || v.getMethodName() == null) {
                            return;
                        }
                        if (v.getClassName().isBlank() || v.getMethodName().isBlank()) {
                            return;
                        }
                        Class<?> c;
                        try {
                            c = Class.forName(v.getClassName());
                            String constructorName = lowerFirstChar(c.getSimpleName());
                            Object obj = applicationContext.getBean(constructorName);
                            Method method;
                            if (v.getParm() == null) {
                                method = ReflectionUtils.findMethod(
                                        obj.getClass(), v.getMethodName());
                                assert method != null;
                                ReflectionUtils.invokeMethod(method, obj);
                            } else {
                                method = ReflectionUtils.findMethod(
                                        obj.getClass(), v.getMethodName(), String.class);
                                assert method != null;
                                ReflectionUtils.invokeMethod(method, obj, JSONUtil.toList(v.getParm(), String.class).toArray());
                            }
                        } catch (ClassNotFoundException e) {
                            log.error("Error: {}", e.getMessage());
                        }
                    }, cron);
            ScheduledFuture<?> future = Objects.requireNonNull(
                            registrar.getScheduler())
                    .schedule(cronTask.getRunnable(),
                            cronTask.getTrigger());
            CRON_TASK_CONCURRENT_HASH_MAP.put(v.getId(), cronTask);
            assert future != null;
            SCHEDULED_FUTURE_CONCURRENT_HASH_MAP.put(v.getId(), future);
        }
    }

    public boolean exists(List<Dynamique> sysCronList, Integer id) {
        return sysCronList.stream().anyMatch(v -> Objects.equals(v.getId(), id));
    }

    @PreDestroy
    public void destroy() {
        registrar.destroy();
    }

    public static String lowerFirstChar(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    public static String convertDateTime2Cron(DateTime dateTime) {
        return dateTime.second()
                + " " + dateTime.minute()
                + " " + dateTime.hour(true)
                + " " + dateTime.dayOfMonth()
                + " " + dateTime.month()
                + " ?";
    }

    public static DateTime convertCron2DateTime(String cron) {
        String[] strings = cron.split(" ");
        int cronSize = 6;
        if (strings.length != cronSize) {
            return null;
        }
        String dateStr = strings[strings.length - 1].split("-")[0] + "-" + strings[strings.length - 3]
                + "-" + strings[strings.length - 4] + " " + strings[2] + ":" + strings[1]
                + ":" + strings[0];
        return new DateTime(dateStr, "YYYY-MM-DD HH:mm:ss");
    }
}
